/*
 * Copyright (C) 2011 by Anton Novikau
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

package com.googlecode.gplaces.parser;

import com.googlecode.gplaces.core.QuotaLimitException;
import com.googlecode.gplaces.data.Place;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.util.ArrayList;
import java.util.List;

/**
 * Parser implementation that process input data in JSON format to more suitable
 * list of objects form.
 * 
 * @author Anton Novikov
 */
public final class PlacesParser {

    /**
     * Size of buffer in chars to copy from input stream to writer.
     */
    private static final int BUFFER_SIZE = 1024;

    // status key to indentify if response valid for processing.
    private static final String STATUS_KEY = "status";

    // results key to obtain array of Places result objects.
    private static final String RESULTS_KEY = "results";

    // geometry key
    private static final String GEOMETRY_KEY = "geometry";

    // location key to obtain particular place geolocation coordinates.
    private static final String LOCATION_KEY = "location";

    // latitiude key
    private static final String LATITUDE_KEY = "lat";

    // longitude key
    private static final String LONGITUDE_KEY = "lng";

    // id key of particular place.
    private static final String PLACE_ID_KEY = "id";

    // name key of particular place.
    private static final String PLACE_NAME_KEY = "name";

    // raiting key of parricular place which is optional parameter in JSON
    // response.
    private static final String RAITING_KEY = "rating";

    /**
     * Creates an instance of PlacesParser. Private constructor is to prevent
     * direct isntance creation of utility class.
     */
    private PlacesParser() {}

    /**
     * Converts stream data to JSON string representation and parse it to more
     * suitable object form.
     * <p>
     * <b>Note:</b> do not forget to close opened stream
     * 
     * @param data
     * @return
     * @throws JSONException
     * @throws IOException
     * @throws QuotaLimitException
     */
    public static List<Place> parseData(InputStream data) throws JSONException, IOException,
            QuotaLimitException {
        JSONObject jsonData = new JSONObject(streamToString(data));
        String status = jsonData.getString(STATUS_KEY);
        List<Place> placesResult;
        // validate response before parsing.
        switch (Status.valueOf(status)) {
            case REQUEST_DENIED:
            case INVALID_REQUEST:
                throw new IllegalStateException(
                        "Some of required parameter were missed. Check the service URL.");
            case OVER_QUERY_LIMIT:
                throw new QuotaLimitException(
                        "Free usage plan exceed the number of requests. Upgrade your plan.");
            case OK:
                placesResult = parseResults(jsonData.getJSONArray(RESULTS_KEY));
                break;
            case ZERO_RESULTS:
                placesResult = new ArrayList<Place>();
                break;
            default:
                // should never occurs if server is not changed.
                throw new IllegalStateException("Status is not supported.");
        }
        return placesResult;
    }

    // parsed Place results from JSON.
    private static List<Place> parseResults(JSONArray jsonResults) throws JSONException {
        int placesSize = jsonResults.length();
        List<Place> placesResult = new ArrayList<Place>(placesSize);
        Place place;
        for (int i = 0; i < placesSize; i++) {
            place = new Place();
            placesResult.add(place);
            JSONObject jsonPlace = jsonResults.getJSONObject(i);
            JSONObject jsonLocation = jsonPlace.getJSONObject(GEOMETRY_KEY).getJSONObject(
                    LOCATION_KEY);
            place.setLatitude(jsonLocation.getDouble(LATITUDE_KEY));
            place.setLongitude(jsonLocation.getDouble(LONGITUDE_KEY));
            place.setId(jsonPlace.getString(PLACE_ID_KEY));
            place.setName(jsonPlace.getString(PLACE_NAME_KEY));
            // if place is not raited set raiting value to 0.
            place.setRaiting(jsonPlace.has(RAITING_KEY) ? jsonPlace.getDouble(RAITING_KEY) : 0);
        }
        return placesResult;
    }

    /**
     * Copies content of input stream to string.<br/>
     * 
     * @param input source stream to copy from
     * @throws IOException when unable to read source stream.
     */
    private static String streamToString(InputStream input) throws IOException {
        char[] buf = new char[BUFFER_SIZE];
        int len;
        Reader reader = new InputStreamReader(input, "UTF-8");
        StringBuilder output = new StringBuilder();
        while ((len = reader.read(buf)) > 0) {
            output.append(buf, 0, len);
        }
        return output.toString();
    }

    /**
     * All response status values supported by Google Places search service.
     * 
     * @author Anton Novikov
     */
    private static enum Status {
        OK, ZERO_RESULTS, OVER_QUERY_LIMIT, REQUEST_DENIED, INVALID_REQUEST
    }
}
